package com.bluexmicro.android.ota

import android.bluetooth.BluetoothGatt
import android.bluetooth.BluetoothGattCharacteristic
import android.os.Build
import android.util.Log
import com.bluexmicro.android.coroutineble.BluetoothPeripheral
import com.bluexmicro.android.coroutineble.model.BleResponse
import com.bluexmicro.android.coroutineble.toHexString
import com.bluexmicro.android.coroutineble.toUInt8
import com.bluexmicro.android.ota.entity.Block
import com.bluexmicro.android.ota.entity.Constant
import com.bluexmicro.android.ota.entity.OtaTask
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.withTimeout
import kotlin.jvm.Throws

/**
 * 一个Firmware（or Material）被分成多个Block，一个Block有多个Segment
 *
 *  以下步骤理解为两个For循环
 *  for(block in Blocks){
 *     //1. send start block command
 *    for(segment in block.segments){
 *        //2. send segment data
 *    }
 *  }
 *  //3. send end command
 *
 * 1. 判断 OtaTask 中是否还有Block，有：进行第2步，否：第5步
 * 2. 发现请求开始Block n 的命令，成功：进行第3步，失败：第6步
 * 3. 判断Block中是否还有Segment，有：进行第4步，否：回到第1步
 * 4. 发送Segment数据后回到第3步
 * 5. 发送 Transfer End 命令，结束流程
 * 6. 错误，结束流程
 */
class BasicProcess(
    private val peripheral: BluetoothPeripheral,
    private val otaTask: OtaTask,
    private val _ctrl: BluetoothGattCharacteristic,
    private val _data: BluetoothGattCharacteristic,
    private val mtuSize: Int,
    private val fastMode: Boolean
) {
    val TAG = "BasicProcess"

    /*
       waitResponseAndSplitData() 内部会阻塞协程，要先执行 requestStartCommand() 才会有response，
       但是response响应速度比waitResponseAndSplitData()执行的快，因此使用combineTransform操作符，同时执行
       waitResponseAndSplitData() 和 requestStartCommand()
        */
    fun execute(): Flow<Float> =
        waitResponseAndSplitData().combineTransform(requestStartCommand()) {  _, _ ->
            peripheral.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
            delay(1000)
            transportDataFlow().collect {
                emit(it)
            }
        }

    @Throws(IllegalStateException::class)
    private fun Boolean.throwExceptionIfFailed(msg: String) {
        if (this.not()) throw IllegalStateException(msg)
    }

    @Throws(IllegalStateException::class)
    private fun requestStartCommand() = flow {
        withTimeout(5500) {
            peripheral.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_BALANCED)
            delay(500)
            var cmd = otaTask.getRequestStartCommand(true)
            Log.w(TAG, "StartCommand1 -> ${cmd.toHexString(',')}")
            //0013007E02557F6CE30000
            var result = peripheral.write(_ctrl, cmd)
            if (!result) {
                cmd = otaTask.getRequestStartCommand(false)
                Log.w(TAG, "StartCommand2 -> ${cmd.toHexString(',')}")
                //由于版本原因，后面新增类型判断（固件和素材）,因此命令新增一个参数，这里为了减少用户选择，这里选择试错的方式进行版本兼容
                delay(50)
                result = peripheral.write(_ctrl, cmd)
            }
//            //在SDK3.0后发送开始命令后需要发起连接参数更新，外设才会进入OTA流程
//            peripheral.requestConnectionPriority(BluetoothGatt.CONNECTION_PRIORITY_HIGH)
//            delay(1000)
            result.throwExceptionIfFailed("Request Start Failed!")
            emit(result)
        }
    }

    private fun waitResponseAndSplitData() = flow {
        withTimeout(15000) {
            //挂起，等待response
            val bytes = peripheral.responseShared.map {
                if (it is BleResponse.OnCharacteristicChanged) {
                    val flag1 = it.characteristic.uuid == _ctrl.uuid
                    val flag2 = it.characteristic.value[0] == Constant.RESPONSE_CTRL_OTA_START
                    if (flag1 && flag2) it.characteristic.value else null
                } else {
                    null
                }
            }.first {
                it != null
            } ?: throw IllegalStateException("Unknown")
            //有结果了，继续
            if (bytes[0] == Constant.RESPONSE_CTRL_OTA_START) {
                val maxSegmentNumInBlock: Int = bytes[2].toUInt8() * 8
                val status: Int = bytes[1].toUInt8()
                if (status == 0) {
                    otaTask.splitData(maxSegmentNumInBlock, mtuSize)
                    emit(true)
                    return@withTimeout
                } else {
                    throw IllegalStateException("Request Start OTA Failed, Remote Device return status $status ")
                }
            }
        }
    }

    private fun transportDataFlow() = flow {
        for (block in otaTask.blocks) {
            Log.d(TAG, "block.segments.size: ${block.segments.size}")
            var timeout = block.segments.size * 80L
            if (timeout < 3000L) timeout = 3000L
            //请求开始区块
//                peripheral.write(_ctrl, block.requestNewBlockCommand)
//                    .throwExceptionIfFailed("Request Block(${block.index}) failed!")
            requestBlock(block)
            withTimeout(timeout) {
                val progress = block.index * 1f / otaTask.blocks.size
                emit(progress)
                for (segment in block.segments) {
                    peripheral.writeWithoutResponse(_data, segment.data)
                        .throwExceptionIfFailed("Send Segment(${segment.index}) of Block(${block.index}) failed!")
                }
            }
        }
        emit(0.9999f)
        peripheral.write(_ctrl, otaTask.requestTransportEndCommand)//忽略错误
        if (fastMode) {
            peripheral.write(_ctrl, byteArrayOf(0x0A))
                .throwExceptionIfFailed("Apply failed!")
        }
        emit(1f)
    }

    /**
     * OTA的命令可能被其他write操作打断（mDeviceBusy），这里做下重试
     */
    private suspend fun requestBlock(block: Block) {
        for (i in 0..2) {
            val res = peripheral.write(_ctrl, block.requestNewBlockCommand)
            if (res) return
            delay(500)
        }
        throw IllegalStateException("Request Block(${block.index}) failed!")
    }
}